package com.kvn.universal.filter.lock;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Condition;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import redis.clients.jedis.Jedis;

/**
 * Jedis实现分布式可重入锁
 * 
 * @author tianyou.yang
 *
 */
public class RedisReentrantLock implements RedisLock, Comparable<RedisReentrantLock> {

	private final static Logger log = LoggerFactory.getLogger(RedisReentrantLock.class);

	private static ThreadLocal<Map<String, RedisReentrantLock>> lock = new ThreadLocal<Map<String, RedisReentrantLock>>();

	/**
	 * Lock key path.
	 */
	String lockKey;

	/**
	 * 锁的过期时间，设置过小，遇到一些耗时任务时，将导致提前释放锁，最终导致混乱
	 */
	long expireMsecs = TimeUnit.MINUTES.toMillis(30); // 30min

	/**
	 * 申请锁的等待时间，超过这个时间中断线程.
	 */
	long timeout = 30 * 60 * 1000;

	AtomicLong holdCount = new AtomicLong(0);

	boolean locked = false;

	Thread currentThread;

	private RedisReentrantLock(String key) {
		currentThread = Thread.currentThread();
		this.lockKey = key;
	}

	public String getKey() {
		return this.lockKey;
	}

	/**
	 * 使用默认参数获取锁
	 *
	 * @param key
	 * @return
	 */
	public static RedisReentrantLock getLock(String key) {
		RedisReentrantLock rtLock;
		if (lock.get() == null) {
			lock.set(new HashMap<String, RedisReentrantLock>());
		}
		RedisReentrantLock oLock = lock.get().get(key);
		if (oLock != null && oLock.getKey().equals(key)) {
			rtLock = oLock;
		} else {
			rtLock = new RedisReentrantLock(key);
			lock.get().put(key, rtLock);
		}
		return rtLock;
	}

	/**
	 * 使用自定义方式获取锁
	 *
	 * @param key
	 *            锁关键字
	 * @param expireTime
	 *            锁过期时间
	 * @param timeout
	 *            申请锁的timeout时间，超过该时间线程中断停止业务。
	 * @return
	 */
	public static RedisReentrantLock getLock(String key, long expireTime, long timeout) {
		RedisReentrantLock rtLock = getLock(key);
		rtLock.expireMsecs = expireTime;
		rtLock.timeout = timeout;
		return rtLock;
	}

	@Override
	public void lock() {
		try {
			boolean gotLock = tryLock(timeout, TimeUnit.MILLISECONDS);
			if (!gotLock) {
				throw new InterruptedException("获取锁超时.");
			}
		} catch (InterruptedException e) {
			log.error("线程中断", e);
			Thread.currentThread().interrupt();
		}
	}

	public void lockInterruptibly(Jedis jedis) throws InterruptedException {
		if ("OK".equals(jedis.set(lockKey, "LOCKED", "NX", "PX", expireMsecs))) {
			// lock acquired
			holdCount.incrementAndGet();
			locked = true;
		} else {
			locked = false;
		}

	}

	@Override
	public boolean tryLock() {
		try {
			tryLock(0, TimeUnit.MILLISECONDS);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		return locked;
	}

	@Override
	public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
		if (locked && Thread.currentThread().equals(currentThread)) {
			holdCount.incrementAndGet();
			return locked;
		}
		long timeout = unit.toMillis(time);
		synchronized (lockKey.intern()) { // redis资源分配策略，拥有同一个key的锁，只能排队使用一个jedis资源。
			try (Jedis jedis = RedisProvider.getClient()) {
				while (timeout >= 0 && (locked != true)) {
					timeout -= 100;
					if (timeout >= 100) {
						Thread.sleep(100);
					}
					lockInterruptibly(jedis);
				}
			}
		}
		return locked;
	}

	@Override
	public void unlock() {
		if (!locked) {
			return;
		}
		if (locked && Thread.currentThread().equals(currentThread) && holdCount.getAndDecrement() > 1) {
			return;
		} else {
			try (Jedis jedis = RedisProvider.getClient()) {
				if (locked) {
					// 当得到锁的逻辑没有执行完而锁过期时，会导致误删其他锁。应慎重设置锁过期时间。
					jedis.del(lockKey);
					locked = false;
					lock.get().remove(lockKey);
				}
			}
		}
	}

	@Override
	public Condition newCondition() {
		throw new UnsupportedOperationException();
	}

	@Override
	public void lockInterruptibly() throws InterruptedException {
		throw new UnsupportedOperationException();
	}

	@Override
	public boolean equals(Object obj) {
		if (obj == null)
			return false;
		if (obj instanceof RedisReentrantLock) {
			return lockKey.equals(((RedisReentrantLock) obj).lockKey);
		} else {
			return false;
		}
	}

	@Override
	public int compareTo(RedisReentrantLock o) {
		return this.lockKey.compareTo(o.lockKey);
	}

	@Override
	public boolean isLocked() {
		return locked;
	}
}
